#include "NetworkControl_Test.h"

#include "server/net/NetworkControl.h"
#include "misc/Event.h"
#include "server/misc/Config.h"
#include "misc/Debug.h"
#include "server/multiProtocol/multiRing.h"
#include "server/multiProtocol/multiDiscovery.h"
#include "server/multiProtocol/multiJoin.h"
#include <Poco/Net/SocketAddress.h>
#include <Poco/Exception.h>
#include <iomanip>
#include <string.h>
#include <iostream>
/*********************************************************/
std::string NetworkControlTest::name()
{
	return("NetworkControl");
}
/*********************************************************/
bool NetworkControlTest::runUnitTests()
{
	bool result = true;

	if ( !cleanStartStopTest() ) result = false;
	if ( !doubleStartStopTest() ) result = false;
	if ( !doubleStopTest() ) result = false;
	if ( !idleStopTest() ) result = false;
	if ( !eventFlagTest() ) result = false;
	if ( !ringCreationTest() ) result = false;
	if ( !ringJoinSingleNodeTest() ) result = false;
	if ( !ringJoinTest() ) result = false;
	if ( !multiDiscoveryReplyTest() ) result = false;
	if ( !JoinTwoNodeRingTest() ) result = false;

	return( result ) ;
}
/*********************************************************/
bool NetworkControlTest::JoinTwoNodeRingTest()
{
	std::cout << "Running: multiJoinTwoNodeRingTest()";
	std::cout.flush();

	try {
		// Start the networkcontroller
		NetworkControl& net = NetworkControl::Instance();
		net.start();

		// Build an imaginary ring into memory
		MultiRing	mRing;
		mRing.setAddr("0.0.0.0:6000");
		mRing.setPredecessorAddr("0.0.0.0:6001");
		mRing.setSuccessorAddr("0.0.0.0:6001");
		mRing.setHash("ABF4432224");
		mRing.execute();

		mRing.setAddr("0.0.0.0:6001");
		mRing.setPredecessorAddr("0.0.0.0:6000");
		mRing.setSuccessorAddr("0.0.0.0:6000");
		mRing.setHash("FFFFFFFFFFFF");
		mRing.execute();

		// Attempt to join a 3rd node to the ring
		MultiJoin	mJoin;
		mJoin.setPred("0.0.0.0:6001");
		mJoin.setAddr("0.0.0.0:6002");
		mJoin.setSucc("0.0.0.0:6000");
		mJoin.setHash("FFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
		mJoin.execute();
	
		// Build the string we expect to get returned from the networkcontrol class
		std::ostringstream	ostr;
		ostr << "Ring in multicastgroup: " << "239.0.0.1:5001" << std::endl;
		ostr << "--------------------------------------" << std::endl;
		ostr << std::setw(25) << std::left << "Addr" << std::setw(25) << "Pred" << std::setw(25) << "Succ" << std::setw(40) << "Hash" << std::endl;
		ostr << std::setw(25) << "0.0.0.0:6000" << std::setw(25) << "0.0.0.0:6002" << std::setw(25) << "0.0.0.0:6001" << std::setw(40) << "ABF4432224" << std::endl;
		ostr << std::setw(25) << "0.0.0.0:6001" << std::setw(25) << "0.0.0.0:6000" << std::setw(25) << "0.0.0.0:6002" << std::setw(40) << "FFFFFFFFFFFF" << std::endl;
		ostr << std::setw(25) << "0.0.0.0:6002" << std::setw(25) << "0.0.0.0:6001" << std::setw(25) << "0.0.0.0:6000" << std::setw(40) << "FFFFFFFFFFFFFFFFFFFFFFFFFFFFF" << std::endl;

		// Get the ring information & check if it's what we expect it to be
		if (strcmp(ostr.str().c_str(), net.getRingInformation().c_str()) == 0 ) {
			std::cout << "... success!" << std::endl;
			net.stop();
			return true;
		} else {
			std::cout << "... failed! Unexpected output from networkcontrol!" << std::endl;
			std::cout << "NetworkControl returned: " << std::endl << net.getRingInformation() << std::endl;
			std::cout << "Expected: " << std::endl << ostr.str() << std::endl;
			net.stop();
			return false;
		}
	} catch(...) {
		std::cout << "... failed! Caught unexpected exception!" << std::endl;
		return false;
	}

	std::cout << "... failed!" << std::endl;
	return false;
}
/*********************************************************/
bool NetworkControlTest::multiDiscoveryReplyTest()
{
	std::cout << "Running: multiDiscoveryReplyTest()";
	std::cout.flush();

	try {	
		// Get the configuration singleton
		Config& cfg = Config::Instance();

		// Start the networkcontroller
		NetworkControl& net = NetworkControl::Instance();
		net.start();

		// Wait a bit, so networkcontrol can start a ring
		while ( !net.getRingActive() ) {
			sleep(1);
		}

		// Ok a ring is active... now we send a discovery & check for the multiring reply
		MultiNet mNet(cfg.getNetworkInterface());
		mNet.start("TRAFFIC_");
		mNet.joinGroup("239.0.0.1", 5001);
		mNet.setLoopback(true);

		MultiDiscovery	mDisc;
		mNet.send(mDisc.serialize());

		// We now have to wait some time for the multiring reply
		unsigned long time = (MULTICAST_RING_CREATE_PAUSE/1000000);
		while ( time > 0 ) {
			sleep(1);
			time = time - 1;

			while ( mNet.available() ) {
				std::string msg = mNet.pop();
				MultiMsg* mMsg = 0;

				try {
					mMsg = MultiMsg::deserialize(msg);
					MultiRing* mRing = dynamic_cast<MultiRing*>(mMsg);
					if ( mRing ) {
						std::cout << "... success!" << std::endl;
						delete(mMsg);
						mNet.stop();
						net.stop();
						return true;
					} else {
						delete(mMsg);
					}
				} catch (...) {
					mMsg = 0;
				}
			}
		}

		mNet.stop();
		net.stop();
	} catch(...) {
		std::cout << "... failed! Caught unknown exception, expected none" << std::endl;
		return false;
	}

	std::cout << "... failed!" << std::endl;
	return false;
}
/*********************************************************/
bool NetworkControlTest::ringJoinSingleNodeTest() 
{
	std::cout << "Running: ringJoinSingleNodeTest()";
	std::cout.flush();
	
	try {
		// Initialize the necessary classes & singletons
		NetworkControl& net = NetworkControl::Instance();
		Config& cfg = Config::Instance();

		MultiNet mNet(cfg.getNetworkInterface());
		mNet.start("TRAFFIC_");
		mNet.joinGroup("239.0.0.1", 5001);
		mNet.setLoopback(true);

		net.start();

		// Send the imaginary rings information accross the network
		mNet.send("<MGS type='RING'><hash>0</hash><pred>0.0.0.0:5000</pred><succ>0.0.0.0:5000</succ><addr>0.0.0.0:5000</addr></MGS>");

		// Wait until the networkcontrol class has automatically joined the ring
		while ( !net.getRingActive() ) {
			sleep(1);
		}

		// Build the string we expect to get returned from the networkcontrol class
		std::ostringstream	ostr;
		ostr << "Ring in multicastgroup: " << "239.0.0.1:5001" << std::endl;
		ostr << "--------------------------------------" << std::endl;
		ostr << std::setw(25) << std::left << "Addr" << std::setw(25) << "Pred" << std::setw(25) << "Succ" << std::setw(40) << "Hash" << std::endl;
		ostr << std::setw(25) << "0.0.0.0:5000" << std::setw(25) << net.getLocalAddress() << std::setw(25) << net.getLocalAddress() << std::setw(40) << "0" << std::endl;
		ostr << std::setw(25) << net.getLocalAddress() << std::setw(25) << "0.0.0.0:5000" << std::setw(25) << "0.0.0.0:5000" << std::setw(40) << "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" << std::endl;

		// Get the ring information & check if it's what we expect it to be
		if (strcmp(ostr.str().c_str(), net.getRingInformation().c_str()) == 0 ) {
			std::cout << "... success!" << std::endl;
			net.stop();
			return true;
		} else {
			std::cout << "... failed! Unexpected output from networkcontrol!" << std::endl;
			std::cout << "NetworkControl returned: " << std::endl << net.getRingInformation() << std::endl;
			std::cout << "Expected: " << std::endl << ostr.str() << std::endl;
			net.stop();
			return false;
		}
	} catch(...) {
		std::cout << "... failed! Caught unknown exception, expected none" << std::endl;
		return false;
	}

	std::cout << "... failed!" << std::endl;
	return false;
}
/*********************************************************/
bool NetworkControlTest::ringJoinTest()
{
	std::cout << "Running: ringJoinTest()";
	std::cout.flush();

	try {
		// Get the singleton instances
		NetworkControl& net = NetworkControl::Instance();
		Config& cfg = Config::Instance();
		cfg.setActivateProcessingEvent("PROCESSING");
		cfg.setUnicastTrafficEvent("TRAFFIC");
		cfg.setNetworkMulticastGroup("239.0.0.1");

		// Create the multiNet class with the information
		MultiNet mNet(cfg.getNetworkInterface());
		mNet.start("TRAFFIC_");
		mNet.joinGroup("239.0.0.1",5001);
		mNet.setLoopback(true);
		
		// Start the middleware layer & create a fake ring
		net.start();

		// Send the imaginary rings information accross the network
		mNet.send("<MGS type='RING'><hash>0000000000000000000000000000000000000001</hash><pred>127.0.0.1:5003</pred><succ>127.0.0.1:5001</succ><addr>127.0.0.1:5000</addr></MGS>");
		mNet.send("<MGS type='RING'><hash>4000000000000000000000000000000000000001</hash><pred>127.0.0.1:5000</pred><succ>127.0.0.1:5002</succ><addr>127.0.0.1:5001</addr></MGS>");
		mNet.send("<MGS type='RING'><hash>A000000000000000000000000000000000000001</hash><pred>127.0.0.1:5001</pred><succ>127.0.0.1:5003</succ><addr>127.0.0.1:5002</addr></MGS>");
		mNet.send("<MGS type='RING'><hash>FF00000000000000000000000000000000000001</hash><pred>127.0.0.1:5002</pred><succ>127.0.0.1:5000</succ><addr>127.0.0.1:5003</addr></MGS>");

		// Wait until we succesfully joined the ring
		while ( !net.getRingActive() ) {
			sleep(1);
		}

		// Build the string we expect to get returned from the networkcontrol class
		std::ostringstream	ostr;
		ostr << "Ring in multicastgroup: " << "239.0.0.1:5001" << std::endl;
		ostr << "--------------------------------------" << std::endl;
		ostr << std::setw(25) << std::left << "Addr" << std::setw(25) << "Pred" << std::setw(25) << "Succ" << std::setw(40) << "Hash" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5000" << std::setw(25) << "127.0.0.1:5003" << std::setw(25) << "127.0.0.1:5001" << std::setw(40) << "0000000000000000000000000000000000000001" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5001" << std::setw(25) << "127.0.0.1:5000" << std::setw(25) << net.getLocalAddress() << std::setw(40) << "4000000000000000000000000000000000000001" << std::endl;
		ostr << std::setw(25) << net.getLocalAddress() << std::setw(25) << "127.0.0.1:5001" << std::setw(25) << "127.0.0.1:5002" << std::setw(40) << "7000000000000000000000000000000000000001" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5002" << std::setw(25) << net.getLocalAddress() << std::setw(25) << "127.0.0.1:5003" << std::setw(40) << "A000000000000000000000000000000000000001" << std::endl;
		ostr << std::setw(25) << "127.0.0.1:5003" << std::setw(25) << "127.0.0.1:5002" << std::setw(25) << "127.0.0.1:5000" << std::setw(40) << "FF00000000000000000000000000000000000001" << std::endl;

		// Get the ring information & check if it's what we expect it to be
		if (strcmp(ostr.str().c_str(), net.getRingInformation().c_str()) == 0 ) {
			std::cout << "... success!" << std::endl;
			net.stop();
			return true;
		} else {
			std::cout << "... failed! Unexpected output from networkcontrol!" << std::endl;
			std::cout << "NetworkControl returned: " << std::endl << net.getRingInformation() << std::endl;
			std::cout << "Expected: " << std::endl << ostr.str() << std::endl;
			net.stop();
			return false;
		}
	} catch(...) {
		std::cout << "... failed! Caught unexpected exception!" << std::endl;
		return false;
	}

	std::cout << "... failed! Impossible test outcome!" << std::endl;
	return false;
}
/*********************************************************/
bool NetworkControlTest::ringCreationTest()
{
	std::cout << "Running: ringCreationTest()";
	std::cout.flush();

	try {
		// Get the networkcontrol instance & start it
		NetworkControl& net = NetworkControl::Instance();
		net.start();

		// Wait for the networkcontroller to create a ring
		sleep((MULTICAST_RING_CREATE_PAUSE/1000000) + 1);

		// Check if the networkcontroller started a ring
		std::string	ringinfo = net.getRingInformation();
		
		std::ostringstream ostr;
		ostr << "Ring in multicastgroup: " << "239.0.0.1" << ":" << 5001 << std::endl;
		ostr << "--------------------------------------" << std::endl;
		ostr << std::setw(25) << std::left  << "Addr" << std::setw(25) << "Pred" << std::setw(25) << "Succ" << std::setw(40) << "Hash" << std::endl;
		ostr << std::setw(25) << net.getLocalAddress() << std::setw(25) << net.getLocalAddress() << std::setw(25) << net.getLocalAddress() << std::setw(40) << "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" << std::endl;

		net.stop();

		// Check if the ring message was correclty stored in the network control class
		if ( strcmp(ostr.str().c_str(), ringinfo.c_str()) == 0 ) {
			std::cout << "... success!" << std::endl;
			return true;
		} else {
			std::cout << "... failed! Invalid ring information stored in NetworkControl" << std::endl;
			std::cout << "NetworkControl returned: " << std::endl;
			std::cout << ringinfo << std::endl;
			std::cout << "Expected: " << std::endl;
			std::cout << ostr.str() << std::endl;
			return false;
		}
	} catch(Poco::Exception& e) {
		std::cout << "... failed! caught exception: " << e.displayText() << std::endl;
		return( false );
	} catch(...) {
		std::cout << "... failed! caught unknown exception!" << std::endl;
		return(false);
	}

	std::cout << "... success!" << std::endl;
	return ( true );
}
/*********************************************************/
bool NetworkControlTest::eventFlagTest()
{
	std::cout << "Running: eventFlagTest()";
	std::cout.flush();

	try {
		// Get the configuration instance & set some basic values ( events are necessary since the systemroot normally sets these )
		Config& cfg = Config::Instance();
		cfg.setActivateProcessingEvent("PROCESSING");
		cfg.setUnicastTrafficEvent("TRAFFIC");

		Event* event = Event::Instance(cfg.getActivateProcessingEvent());
		NetworkControl& net = NetworkControl::Instance();

		// get a unicast class
		Poco::Net::NetworkInterface iface = Poco::Net::NetworkInterface::forName(cfg.getNetworkInterface());
		UDPNet* unicast = new( UDPNet )(iface.address().toString(), 5000);
		unicast->start(cfg.getUnicastTrafficEvent());
		unicast->setResponseAddress(iface.address().toString(), 4000);

		net.start();

		unicast->send("<GS><job type='exe'><cmd>ZWNobyAndGVz</cmd></job></GS>");
		Debug::Instance().log("TEST.txt");
		event->wait();
		
		unicast->stop();
		net.stop();
		delete( unicast );
	} catch(Poco::Exception& e) {
		std::cout << "... failed! caught exception: " << e.displayText() << std::endl;
		return( false );
	} catch(...) {
		std::cout << "... failed! caught unknown exception!" << std::endl;
		return(false);
	}

	std::cout << "... success!" << std::endl;
	return( true );
}
/*********************************************************/
bool NetworkControlTest::doubleStopTest()
{
	std::cout << "Running: doubleStopTest()";
	std::cout.flush();
	
	try {
		// Get an instance of the networkcontroller ( start the controller )
		NetworkControl& net = NetworkControl::Instance();

		// Stop the controller by Destructing it
		net.start();
		net.stop();
		net.stop();
	} catch(Poco::Exception& e) {
		std::cout << "... Exception caught: " << e.displayText() << "... Failed!" << std::endl;
		return( false);
	} catch(...) {
		std::cout << "... Unknown exception caught, stopping the NetworkController twice... Failed!" << std::endl;
		return( false );
	}
	
	// We had a clean destruction
	std::cout << "... success!" << std::endl;
	return( true );
}
/*********************************************************/
bool NetworkControlTest::idleStopTest()
{
	std::cout << "Running: idleStopTest()";
	std::cout.flush();
	
	try {
		// Get an instance of the networkcontroller ( start the controller )
		NetworkControl& net = NetworkControl::Instance();

		// Stop the controller by Destructing it
		net.stop();
	} catch(Poco::Exception& e) {
		std::cout << "... Exception caught: " << e.displayText() << "... Failed!" << std::endl;
		return( false);
	} catch(...) {
		std::cout << "... Unknown exception caught, stopping an idle NetworkController... Failed!" << std::endl;
		return( false );
	}
	
	// We had a clean destruction
	std::cout << "... success!" << std::endl;
	return( true );
}
/*********************************************************/
bool NetworkControlTest::doubleStartStopTest()
{
	std::cout << "Running: doubleStartStopTest()";
	std::cout.flush();
	
	try {
		// Get an instance of the networkcontroller ( start the controller )
		NetworkControl& net = NetworkControl::Instance();

		// Stop the controller by Destructing it
		net.start();
		net.stop();
		net.start();
		net.stop();
	} catch(Poco::Exception& e) {
		std::cout << "... Exception caught: " << e.displayText() << "... Failed!" << std::endl;
		return( false);
	} catch(...) {
		std::cout << "... Unknown exception caught, trying to stop the NetworkController... Failed!" << std::endl;
		return( false );
	}
	
	// We had a clean destruction
	std::cout << "... success!" << std::endl;
	return( true );
}
/*********************************************************/
bool NetworkControlTest::cleanStartStopTest()
{
	std::cout << "Running: cleanStartStopTest()";
	std::cout.flush();
	
	try {
		// Make sure we aren't automatically starting a ring
		Config& cfg = Config::Instance();
		cfg.setActivateProcessingEvent("PROCESSING");
		cfg.setUnicastTrafficEvent("TRAFFIC");

		// Get an instance of the networkcontroller
		NetworkControl& net = NetworkControl::Instance();

		// Stop the controller by Destructing it
		net.start();
		net.stop();
	} catch(Poco::Exception& e) {
		std::cout << "... Exception caught: " << e.displayText() << "... Failed!" << std::endl;
		return( false);
	} catch(...) {
		std::cout << "... Unknown exception caught, trying to stop the NetworkController... Failed!" << std::endl;
		return( false );
	}
	
	// We had a clean destruction
	std::cout << "... success!" << std::endl;
	return( true );
}
